﻿using System;
using System.Collections.Generic;
using System.Text;

namespace IMMeDotNet {

	/// <summary>
	/// Represents a connected IM-me device.
	/// </summary>
	public class Connection {
		
		/// <summary>
		/// The host that controls the connections.
		/// </summary>
		public Host Host { get; private set; }
		
		/// <summary>
		/// The connection ID.
		/// </summary>
		public byte ID { get; private set; }
		
		/// <summary>
		/// The primary contact using the connection (the person logged into the IM-me device).
		/// </summary>
		public Contact Contact { get; private set; }

		/// <summary>
		/// The friends of the primary contact.
		/// </summary>
		public List<Contact> Friends { get; private set; }
		
		/// <summary>
		/// Creates an instance of the <see cref="Connection"/> class.
		/// </summary>
		public Connection(Host host, byte id, Contact contact) {
			this.Host = host;
			this.ID = id;
			this.Contact = contact;
			this.Friends = new List<Contact>();
		}

		// Stores outgoing messages.
		private Dictionary<uint, Message> outbox = new Dictionary<uint,Message>();
		// Used to generate message indices.
		private byte messageCounter = 0;
		// Used to generate random keys for messages.
		private Random textIDGenerator = new Random((int)(DateTime.Now.Ticks / (uint)1E7));
		// Stores the contact that a message went to (by its ID).
		private Dictionary<uint, Contact> mostRecentMessageToFriend = new Dictionary<uint, Contact>();

		/// <summary>
		/// Build an <see cref="OutgoingUsbMessage"/> from a <see cref="Message"/> for this <see cref="Connection"/>.
		/// </summary>
		internal OutgoingUsbMessage BuildMessage(Message message) {

			// Creates an ID for the text message.
			uint textID;
			lock (outbox) {
				do {
					textID = (uint)textIDGenerator.Next();
				} while (outbox.ContainsKey(textID));
				outbox.Add(textID, message);
			}

			var textMessage = new OutgoingUsbMessage(0x00, 0x06, 0x02);

			while (++messageCounter == 0) ;
			textMessage.Write(messageCounter);
			textMessage.Write(this.ID);

			textMessage.Write(message.Recipient.ID);
			textMessage.Write((uint)textID);
			textMessage.Write(message.Sender.ID);

			textMessage.Write(message.Text);

			return textMessage;

		}

		/// <summary>
		/// Fetch and remove a message from the outbox from its acknowledged message ID.
		/// </summary>
		internal Message GetMessageAcknowledged(uint messageID) {
			Message result = null;
			if (this.outbox.TryGetValue(messageID, out result)) {
				this.outbox.Remove(messageID);

				// Set the most recent message delivered to a friend.

				// Remove existing messages to that friend.
				var messagesToRemove = new List<uint>();
				foreach (var item in this.mostRecentMessageToFriend) {
					if (item.Value.ID == result.Sender.ID) {
						messagesToRemove.Add(item.Key);
					}
				}
				foreach (var item in messagesToRemove) this.mostRecentMessageToFriend.Remove(item);
				if (this.mostRecentMessageToFriend.ContainsKey(messageID)) this.mostRecentMessageToFriend.Remove(messageID);
				// Mark that message ID as being the most recent message to go to a particular friend.
				this.mostRecentMessageToFriend.Add(messageID, result.Sender);				
			}
			return result;
		}

		/// <summary>
		/// Identifies a contact based on the last message ID sent to them.
		/// </summary>
		internal Contact GetContactFromLastMessageID(uint messageID) {
			Contact result = null;
			if (this.mostRecentMessageToFriend.TryGetValue(messageID, out result)) {
				this.mostRecentMessageToFriend.Remove(messageID);
			}
			return result;
		}

		/// <summary>
		/// Sends a <see cref="Message"/> to the <see cref="Contact"/> of this connection.
		/// </summary>
		/// <param name="message">The message to send.</param>
		public void SendMessage(Message message) {
			if (message.Recipient.ID != this.Contact.ID) throw new InvalidOperationException("This method can only be used to send messages to the connection's contact.");
			this.Host.SendMessage(this, message);
		}

		/// <summary>
		/// Sends a <see cref="Message"/> to the <see cref="Contact"/> of this connection.
		/// </summary>
		/// <param name="sender">The <see cref="Contact"/> sending the message.</param>
		/// <param name="message">The message to send as a string.</param>
		public void SendMessage(Contact sender, IMMeString message) {
			this.SendMessage(new Message(sender, this.Contact, message));
		}

	}
}
